背景

　　流量匹配推荐系统是面向通信服务提供商设计的新型智能化服务平台。当前移动用户面临套餐种类繁杂、流量需求动态变化、使用习惯差异大等痛点，往往导致资源浪费或资费超出预期，直接影响用户体验与满意度。
　　传统套餐推荐方式依赖人工经验或简单规则匹配，难以实时响应个性化需求变化，缺乏精准性和灵活性，限制了运营商套餐销售转化效率与用户黏性提升。本项目旨在结合深度学习和数据处理技术，构建一套高精度的动态流量套餐推荐引擎，通过深入挖掘用户历史行为、地理位置、消费周期等数据特征，实现对用户流量使用需求的智能感知与预测，在保证用户体验的基础上最大化运营商资源利用率，形成数据驱动的营销决策闭环。
目标
构建基于大规模数据挖掘的深度学习推荐引擎，通过双塔模型结构实现用户与流量套餐的高效向量化匹配。在保障系统稳定性和可扩展性的前提下达成：
1. 需求覆盖率：推荐套餐匹配用户真实使用模式的准确率 ≥92%；
2. 资源利用率：降低用户套餐冗余率（目标减少15%的超量订购或流量浪费）；
3. 动态适应性：通过周期性数据再训练（日/周级），捕捉用户群体行为趋势变化；
4. 模型泛化力：支持对新套餐的冷启动推荐；
5. 架构解耦性：实现特征工程、模型训练、在线服务模块独立演进。
6. 响应效率： 用户进入推荐模块后即时生成个性化方案；
7. 业务转化： 显著提升套餐订购转化率及用户生命周期价值（预期用户资费满意度提升 ≥25%）；
8. 动态适配： 实时识别用户使用场景变化（如流量突增、套餐余量不足）并主动触发精准推荐；
9. 商业协同： 为业务运营提供套餐热度分析、定价策略优化等数据支持。
系统能力要求
1. 数据资源整合能力
● 支持海量历史行为数据的自动化清洗与特征聚合
● 具备多维度用户画像标签体系的批量化构建
● 实现特征存储层的高效检索与管理
2. 智能匹配核心能力
● 双塔模型框架对用户行为模式和套餐属性特征进行解耦学习
● 融合隐式反馈与多目标优化的联合训练机制
● 自动化评估模块持续监控模型对新增套餐的适配表现
3. 工程架构支撑能力
● 高并发服务架构保障推荐接口的大规模稳定调用
● 训练与推理环节的物理隔离及热部署支持
● 基础设施具备模型/特征全生命周期的追溯能力
4. 业务策略拓展能力
● 输出可解释的套餐群体适配性分析报告
● 支持业务策略的配置化干预（如促销加权/套餐优先级）
● 异常模式识别触发模型再训练机制
数据说明
　　本数据基于某运营商脱敏数据集构建，本系统整合6万用户画像、50套核心套餐资源及约18万条交互轨迹三大模块数据，支撑电信行业精准营销场景：用户信息覆盖年龄分布、地域层级和消费能力等维度，通过学生/商务人群标签与5G偏好指数刻画细分需求，其中合约状态字段为存量用户续约策略提供关键依据；套餐库聚焦经济型/无限流量型等市场主流品类，以流量配置（含无限流量标识）、合约周期及5G支持等属性构建分层产品矩阵；用户行为链则通过"浏览-详情-收藏-申办"四级转化漏斗，实时记录用户在数字渠道的决策路径。该数据生态直指电信行业用户ARPU值提升核心诉求，为5G时代从流量经营向价值经营转型提供量化决策基础。
注意查看 data 目录下的 README.md 文件
技术工具
　　本项目基于PyTorch深度学习框架构建双塔推荐架构，依托scikit-learn标准化预处理流程与pandas高效数据操作，结合seaborn可视化诊断工具，形成覆盖数据处理、特征工程、模型训练到效果评估的端到端AI解决方案。各工具链通过统一Python接口实现无缝互操作。
开发工具（IDE）
名称	版本	说明
Jupyter Lab	4.0.8	Jupyter Lab 是由 Project Jupyter 团队开发的新一代交互式计算与开发环境。它继承了 Jupyter Notebook 的核心功能，并融合了多工作流集成、模块化界面和扩展生态系统，成为数据科学、机器学习和科研领域的一体化生产力平台。
分析环境及依赖库
名称	版本	说明
Python	3.9.13	高级解释型编程语言，提供动态类型系统和自动内存管理，支持多范式编程（面向对象、函数式、过程式）。具备丰富的标准库和包生态系统(PyPI)，在科学计算、机器学习和自动化领域具有显著优势。
os		操作系统接口，标准库。
time		时间操作接口，标准库。
random		随机数生成，标准库。
json		JSON编解码器，标准库。
typing		类型提示支持，标准库。
collections		容器数据类型，标准库。
heapq		堆队列算法，标准库。
numpy	1.24.4	基础数值计算库，支持高效的多维数组操作和线性代数运算。
pandas	1.4.4	数据处理与分析库，提供DataFrame结构和数据清洗、转换工具。
scikit-learn	1.0.2	机器学习核心库，提供数据预处理、模型训练与评估工具。
seaborn	0.11.2	统计可视化库，基于matplotlib的高级接口，用于数据分布和关系可视化。
matplotlib	3.7.5	基础绘图库，支持创建静态、动态和交互式可视化图表。
torch（PyTorch）	2.1.0	深度学习框架，提供张量计算、自动微分和GPU加速支持。
tqdm	4.66.1	进度条可视化库，为循环和数据处理提供实时进度反馈。
建模思路
　　通过多源数据整合与异常清洗构建用户画像，经业务特征重构（区域分级/通信强度指数/套餐性价分析），选用双塔模型架构进行特征交互学习，训练阶段采用流行度感知负采样与混合损失优化，最终生成满足多样性的高价值套餐推荐结果。
1. 数据预处理
● 多源数据整合：加载客户信息、套餐详情和用户交互数据
● 关键特征筛选：识别核心消费特征（年龄/月消费/流量使用等）
● 异常值处理：过滤并修正年龄/消费/流量使用范围外的数据
● 数据集划分：按时间序列划分训练/验证/测试集
2. 特征工程
● 用户特征开发：
  ○ 区域等级编码（一线/新一线等）
  ○ 通信强度指数（流量+通话+短信加权）
  ○ 价值分层（消费+客户价值离散化）
● 套餐特征优化：
  ○ 无限流量标准化处理
  ○ 套餐性价比指标
  ○ 业务型分类
● 交互特征构建：
  ○ 行为权重映射
  ○ 用户活跃度指标
#%%
导入库
import os
import json
import numpy as np
import pandas as pd
import random
from typing import List, Dict, Union
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader, TensorDataset
from torch.optim.lr_scheduler import ReduceLROnPlateau
from sklearn.preprocessing import StandardScaler, LabelEncoder, KBinsDiscretizer
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import seaborn as sns
from collections import defaultdict
from tqdm import tqdm
import heapq
import warnings
在Jupyter中内联显示图形
%matplotlib inline
设置中文显示字体为SimHei
plt.rcParams['font.sans-serif'] = 'SimHei'
解决负号'-'等特殊符号显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
使用ggplot样式来绘制图形
plt.style.use('ggplot')
忽略警告信息，以免干扰代码执行过程
warnings.filterwarnings('ignore')
设置全局参数
RANDOM_SEED = 42
torch.manual_seed(RANDOM_SEED)
np.random.seed(RANDOM_SEED)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
#%% md
特征工程-数据探索与预处理
1.加载原始数据查看基本信息。
2.探索性分析用户和套餐数据。
3.筛选用户和套餐数据的关键特征和处理异常值。

考点1：使用pandas读取 “/home/qingjiao/流量套餐推荐系统/data” （推荐使用相对路径 ./data/文件名.csv）目录下的 “客户信息表.csv、套餐信息表.csv、用户套餐交互表.csv” 数据文件，查看前5行、数据基本信息。
#定义读取数据路径
USER_DATA_PATH = "./data/客户信息表.csv"
PLAN_DATA_PATH = "./data/套餐信息表.csv"
USER_AND_PLAN_DATA_PATH = "./data/用户套餐交互表.csv"
#读取用户、套餐、用户套餐交互数据
user_df = pd.read_csv(USER_DATA_PATH)
item_df = pd.read_csv(PLAN_DATA_PATH)
interaction_df = pd.read_csv(USER_AND_PLAN_DATA_PATH)


#查看数据前5行
print("\n用户数据前5行:")
print(user_df.head(5))
print("\n套餐数据前5行:")
print(item_df.head(5))
print("\n交互数据前5行:")
print(interaction_df.head(5))


#查看数据基本信息
print("\n用户数据基本信息:")
print(f"形状: {user_df.shape}")
print(f"列名: {list(user_df.columns)}")
print(f"数据类型:\n{user_df.dtypes}")
print(f"缺失值统计:\n{user_df.isnull().sum()}")
print("\n套餐数据基本信息:")
print(f"形状: {item_df.shape}")
print(f"列名: {list(item_df.columns)}")
print(f"数据类型:\n{item_df.dtypes}")
print(f"缺失值统计:\n{item_df.isnull().sum()}")
print("\n交互数据基本信息:")
print(f"形状: {interaction_df.shape}")
print(f"列名: {list(interaction_df.columns)}")
print(f"数据类型:\n{interaction_df.dtypes}")
print(f"缺失值统计:\n{interaction_df.isnull().sum()}")

考点2：对用户数据进行分析和异常值处理，筛选出年龄（18-100岁）、月消费（10-3000元）、每月数据使用量（0-200GB）在合理范围区间的记录，统计各项数据指标分布并输出绘图。

print("\n" + "="*50)
print("用户数据分析与异常值处理:")
print("="*50)
#查看用户数据分布
print("\n用户数据分布:")
print(f"性别分布: {user_df['gender'].value_counts().to_dict()}")
print(f"区域分布: {user_df['region'].value_counts().to_dict()}")
print(f"用户人群分布: {user_df['user_segment'].value_counts().to_dict()}")
print(f"年龄描述统计:\n{user_df['age'].describe()}")
print(f"月消费描述统计:\n{user_df['monthly_consumption'].describe()}")
#异常值检测1：年龄
age_outliers = user_df[(user_df['age'] < 18) | (user_df['age'] > 100)]
print(f"\n年龄异常值数量: {len(age_outliers)}")
if not age_outliers.empty:
    print("处理年龄异常值：使用中位数替换")
    age_median = user_df['age'].median()
    user_df.loc[(user_df['age'] < 18) | (user_df['age'] > 100), 'age'] = age_median
#异常值检测2：月消费
consume_outliers = user_df[(user_df['monthly_consumption'] < 10) | (user_df['monthly_consumption'] > 3000)]
print(f"\n月消费异常值数量: {len(consume_outliers)}")
if not consume_outliers.empty:
    print("处理月消费异常值：使用分位数替换")
    # 计算5%和95%分位数
    low, high = user_df['monthly_consumption'].quantile([0.05, 0.95])
    user_df.loc[user_df['monthly_consumption'] < low, 'monthly_consumption'] = low
    user_df.loc[user_df['monthly_consumption'] > high, 'monthly_consumption'] = high
#异常值检测3：每月数据使用量
data_outliers = user_df[user_df['avg_monthly_data'] > 200]
print(f"\n每月数据使用量异常值数量: {len(data_outliers)}")
if not data_outliers.empty:
    print("处理数据使用量异常值：使用95%分位数替换")
    high = user_df['avg_monthly_data'].quantile(0.95)
    user_df.loc[user_df['avg_monthly_data'] > 200, 'avg_monthly_data'] = high
#数据分布可视化
print("\n用户特征分布可视化...")
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
#年龄分布
sns.histplot(user_df['age'], bins=30, kde=True, ax=axes[0,0])
axes[0,0].set_title('年龄分布')
axes[0,0].axvline(user_df['age'].median(), color='r', linestyle='dashed', linewidth=1)
#区域分布
sns.countplot(data=user_df, x='region', ax=axes[0,1])
axes[0,1].set_title('区域分布')
axes[0,1].tick_params(axis='x', rotation=45)
#用户分群与月消费关系
sns.boxplot(data=user_df, y='monthly_consumption', x='user_segment', ax=axes[1,0])
axes[1,0].set_title('不同用户分群的月消费分布')
#数据使用与月消费关系
sns.scatterplot(data=user_df, x='avg_monthly_data', y='monthly_consumption', 
                hue='preference_5g', size='preference_5g', sizes=(30, 150), ax=axes[1,1])
axes[1,1].set_title('数据使用与月消费关系(按5G偏好着色)')
plt.tight_layout()
plt.show()

考点3：对流量套餐数据进行分析和异常值处理，筛选出价格（5-500元）在合理范围区间的记录，统计各项数据指标分布并输出绘图。

print("\n" + "="*50)
print("套餐数据分析与异常值处理:")
print("="*50)
#查看套餐数据分布
print("\n套餐数据分布:")
print(f"套餐类型分布: {item_df['package_type'].value_counts().to_dict()}")
print(f"价格描述统计:\n{item_df['price'].describe()}")
print(f"包含数据量描述统计:\n{item_df['included_data'].describe()}")
print(f"通话分钟数描述统计:\n{item_df['call_minutes'].describe()}")
#异常值检测：价格
price_outliers = item_df[(item_df['price'] < 5) | (item_df['price'] > 500)]
print(f"\n价格异常值数量: {len(price_outliers)}")
if not price_outliers.empty:
    print("处理价格异常值：使用分位数替换")
    low, high = item_df['price'].quantile([0.05, 0.95])
    item_df.loc[item_df['price'] < low, 'price'] = low
    item_df.loc[item_df['price'] > high, 'price'] = high
#数据分布可视化
print("\n套餐特征分布可视化...")
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
#套餐类型分布
sns.countplot(data=item_df, x='package_type', ax=axes[0])
axes[0].set_title('套餐类型分布')
axes[0].tick_params(axis='x', rotation=45)
#数据量与价格关系
scatter = sns.scatterplot(data=item_df, x='included_data', y='price', 
                          hue='support_5g', size='support_5g', sizes=(30, 150), ax=axes[1])
axes[1].set_title('数据量与价格关系(按5G支持着色)')
#标记无限流量点
unlimited = item_df[item_df['included_data'] == 999]
if not unlimited.empty:
    for i, row in unlimited.iterrows():
        axes[1].text(row['included_data'], row['price'], "无限流量", 
                    fontsize=8, ha='center', va='bottom')
#合约长度与价格关系
sns.boxplot(data=item_df, y='price', x='contract_length', ax=axes[2])
axes[2].set_title('不同合约长度的价格分布')
plt.tight_layout()
plt.show()

考点4：对用户与套餐的交互数据进行分析，统计用户行为、活跃度指标分布并输出绘图。

print("\n" + "="*50)
print("交互数据分析:")
print("="*50)
#查看交互数据分布
print("\n交互数据分布:")
print(f"事件类型分布: {interaction_df['event_type'].value_counts().to_dict()}")
print(f"时间戳范围: {interaction_df['timestamp'].min()} 至 {interaction_df['timestamp'].max()}")
#分析用户行为分布
print("\n用户行为分布可视化...")
plt.figure(figsize=(10,6))
sns.countplot(data=interaction_df, x='event_type', order=['browse','detail','favor','apply'])
plt.title('用户行为分布')
plt.show()
#分析用户活跃度
print("\n分析用户活跃度...")
user_activity = interaction_df.groupby('user_id').size().reset_index(name='activity_count')
print(f"用户活动次数描述统计:\n{user_activity['activity_count'].describe()}")
plt.figure(figsize=(10,6))
sns.histplot(user_activity['activity_count'], bins=30, kde=True)
plt.title('用户活动次数分布')
plt.axvline(user_activity['activity_count'].median(), color='r', linestyle='dashed', linewidth=1)
plt.show()


特征工程
1.用户特征开发，映射特征编码，创造新特征。
2.套餐特征优化，标准化、分类处理，创造新特征。
3.交互特征构建，建立梯度权重，创造新特征。
4.划分用户塔和套餐塔的数值型与类别型特征，标准化、特征编码。
5.基于流行度感知正负采样，构建模型输入数据集，划分数据集。

考点5：实现用户数据特征处理函数，处理 “地区（一线城市 > 新一线城市 > 二线城市 > 三线及以下）” 映射为 “3,2,1,0” 数值编码，“性别” 二值化为 “M:1,F:0”，业务规则二值化 “合约状态”；通过加权融合 “流量（avg_monthly_data）/通话（avg_monthly_call）/短信使用量（avg_monthly_sms）” 创建 “通信强度特征（comm_intensity）” 并进行通信偏好的 4 级分位数离散化，生成 5 挡离散化的 “用户价值分层（value_segment）”。

def preprocess_users(df):
    # 区域编码
    df['region'] = df['region'].map({
        '一线城市': 3, '新一线城市': 2, '二线城市': 1, '三线及以下': 0
    }).fillna(0).astype(int)
    # 输出区域编码验证信息
    print(f"区域编码后分布: {df['region'].value_counts().to_dict()}")
# 性别编码
df['gender'] = df['gender'].map({'M': 1, 'F': 0}).fillna(0).astype(int)
# 输出性别编码验证信息
print(f"性别编码后分布: {df['gender'].value_counts().to_dict()}")

# 通信强度特征
df['comm_intensity'] = (
    df['avg_monthly_data'] / df['avg_monthly_data'].max() * 0.5 +
    df['avg_monthly_call'] / df['avg_monthly_call'].max() * 0.4 +
    df['avg_monthly_sms'] / df['avg_monthly_sms'].max() * 0.1
)
# 输出通信强度统计信息
print(f"通信强度特征创建: 均值={df['comm_intensity'].mean():.4f}, 标准差={df['comm_intensity'].std():.4f}, 范围=[{df['comm_intensity'].min():.4f}, {df['comm_intensity'].max():.4f}]")

# 合约状态
original_contract_status = df['contract_left'].apply(lambda x: '有合约' if x > 0 else '无合约').value_counts().to_dict()
df['is_contract'] = (df['contract_left'] > 0).astype(int)
# 输出合约状态验证信息
print(f"合约状态标记完成: 原始合约状态 {original_contract_status} → 编码后分布 {df['is_contract'].value_counts().to_dict()}")

# 用户价值分层
df['value_segment'] = pd.cut(
    df['monthly_consumption'] * 0.7 + df['customer_value'] * 0.3,
    bins=5, labels=False
).fillna(0).astype(int)
# 输出价值分层统计信息
segment_counts = df['value_segment'].value_counts().sort_index().to_dict()
print(f"用户价值分层完成: 各层级分布 {segment_counts}")

# 通信偏好离散化
data_encoder = KBinsDiscretizer(n_bins=4, encode='ordinal', strategy='quantile')
call_encoder = KBinsDiscretizer(n_bins=4, encode='ordinal', strategy='quantile')

df['data_level'] = data_encoder.fit_transform(df[['avg_monthly_data']]).astype(int)
df['call_level'] = call_encoder.fit_transform(df[['avg_monthly_call']]).astype(int)
# 输出离散化结果验证
print(f"通信偏好离散化完成: 数据使用等级分布 {df['data_level'].value_counts().sort_index().to_dict()}")
print(f"通信偏好离散化完成: 通话时长等级分布 {df['call_level'].value_counts().sort_index().to_dict()}")

return df

应用用户数据预处理函数
user_df = preprocess_users(user_df)

考点6：实现流量套餐数据特征处理函数，将 “无限流量套餐” 进行标准化处理，统一改为 “300GB” 阈值，新增 “is_unlimited” 二元标识列；创建 “data_value_ratio” 套餐价值指标；按照业务规则对套餐类型分类，划分为 “高性能（流量>30GB且通话>1000分钟）、均衡型（流量 15-30GB 区间）、基础型（流量≤15GB）”。

def preprocess_items(df):
    # 记录原始无限流量套餐数量
    original_unlimited_count = (df['included_data'] == 999).sum()
# 处理无限流量套餐
df['is_unlimited'] = (df['included_data'] == 999).astype(int)
df.loc[df['included_data'] == 999, 'included_data'] = 300
# 输出无限流量套餐处理结果
print(f"无限流量套餐处理: 原始数量={original_unlimited_count}, 标记为无限流量套餐数量={df['is_unlimited'].sum()}")
print(f"  无限流量套餐的数据量已替换为300GB，替换后最大值={df['included_data'].max()}GB")

# 套餐价值评分
df['data_value_ratio'] = df['included_data'] / (df['price'] + 1)
# 输出价值评分统计信息
ratio_stats = df['data_value_ratio'].describe()
print(f"套餐价值评分(data_value_ratio):")
print(f"  最小值={ratio_stats['min']:.2f}, 最大值={ratio_stats['max']:.2f}, 均值={ratio_stats['mean']:.2f}")
print(f"  25%分位数={ratio_stats['25%']:.2f}, 中位数={ratio_stats['50%']:.2f}, 75%分位数={ratio_stats['75%']:.2f}")

# 套餐类型分类
conditions = [
    (df['included_data'] > 30) & (df['call_minutes'] > 1000),
    (df['included_data'] > 15) & (df['included_data'] <= 30),
    (df['included_data'] <= 15)
]
choices = [0, 1, 2]  # 0: 高性能, 1: 均衡型, 2: 基础型
df['package_category'] = np.select(conditions, choices, default=1).astype(int)

# 输出套餐分类分布
category_counts = df['package_category'].value_counts().sort_index()
category_names = {0: '高性能', 1: '均衡型', 2: '基础型'}
print("\n套餐类型分类分布:")
for cat_id, count in category_counts.items():
    name = category_names.get(cat_id, '未知类型')
    percent = count / len(df) * 100
    print(f"  {name}({cat_id}): {count}个套餐, 占比{percent:.1f}%")

return df

应用流量套餐数据预处理函数
item_df = preprocess_items(item_df)

考点7：实现用户-套餐交互数据特征处理函数，通过 “事件类型” 映射建立价值梯度权重（浏览 : 0.1 < 详情 : 0.3 < 收藏 : 0.7 < 申请 : 1.0）生成 “behavior_weight” 特征；基于交互次数创建 “interaction_count” 特征，识别零交互用户；按时间戳排序保留用户对同一套餐的最后一次交互；计算交互矩阵稀疏度。

def preprocess_interactions(interaction_df, user_df):
    # 行为权重映射
    behavior_weight = {'browse': 0.1, 'detail': 0.3, 'favor': 0.7, 'apply': 1.0}
    interaction_df['behavior_weight'] = interaction_df['event_type'].map(behavior_weight)
# 输出权重映射后统计
weight_stats = interaction_df['behavior_weight'].describe()
print(f"  权重映射后: 最小值={weight_stats['min']:.1f}, 最大值={weight_stats['max']:.1f}, 均值={weight_stats['mean']:.2f}")

# 用户活跃度特征
user_interaction_count = interaction_df.groupby('user_id').size()
print(f"  交互用户数量: {len(user_interaction_count)}/{len(interaction_df)} (有交互/总用户)")

user_df['interaction_count'] = user_df['user_id'].map(user_interaction_count).fillna(0)

# 输出活跃度统计
activity_stats = user_df['interaction_count'].describe()
print(f"  用户活跃度统计: 最小值={activity_stats['min']}, 最大值={activity_stats['max']}, 均值={activity_stats['mean']:.2f}")
print(f"  零交互用户数量: {(user_df['interaction_count'] == 0).sum()} (占比{((user_df['interaction_count'] == 0).sum()/len(user_df))*100:.1f}%)")

# 保留最后一条交互记录
interaction_df = interaction_df.sort_values(['user_id', 'timestamp'])
interaction_df = interaction_df.drop_duplicates(['user_id', 'item_id'], keep='last')

user_count = user_df['user_id'].nunique()
item_count = interaction_df['item_id'].nunique()
sparsity = 1 - len(interaction_df) / (user_count * item_count)
print(f"  交互矩阵稀疏度: {sparsity:.4f} (用户数={user_count}, 物品数={item_count})")

return interaction_df, user_df

# 应用用户-套餐交互数据预处理函数
interaction_df, user_df = preprocess_interactions(interaction_df, user_df)
print(f"\n交互数据剩余记录: {interaction_df.shape[0]}")